package com.yuke.cloud.common.core.interceptor;

import com.google.common.collect.Sets;
import com.yuke.cloud.common.base.constant.GlobalConstant;
import com.yuke.cloud.common.base.dto.LoginAuthDto;
import com.yuke.cloud.common.base.dto.UserTokenDto;
import com.yuke.cloud.common.util.JacksonUtil;
import com.yuke.cloud.common.util.PublicUtil;
import com.yuke.cloud.common.util.RedisKeyUtil;
import com.yuke.cloud.common.util.ThreadLocalMap;
import com.yuke.cloud.common.util.annotation.NoNeedAccessAuthentication;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * The class Token interceptor.
 *
 * @author
 */
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {

	@Value("${spring.profiles.active}")
	private String env;//当前激活的配置文件

	//del by wg 20180910
//	@Value("${yuke.oauth2.jwtSigningKey}")
//	private String jwtSigningKey;

	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	private static final String OPTIONS = "OPTIONS";
	private static final String AUTH_PATH1 = "/auth";
	private static final String AUTH_PATH2 = "/oauth";
	private static final String AUTH_PATH3 = "/error";
	private static final String AUTH_PATH4 = "/api";
	private static final String ALIPAY_CALL_URI = "/order/pay/notify";
	private static final String WEIXINPAY_CALL_URI = "/wxpay/notifyWeiXinPay";
	private static final String ALIPAY_RECHARGE_CALL_URI = "/bill/billpay/AliNotify";// add by zn 20191212
	private static final String WEIXINPAY_RECHARGE_CALL_URI = "/bill/billpay/WxNotify";// add by zn 20191212
	private static final String MALL_PATH = "/mall";  // add by wg 20181229

	// add by wg 20190126
	private static final String AUTH_LOGIN_AFTER_URL = "/user/loginAfter/**";
	private static final String AUTH_LOGOUT_URL = "/user/logout";
	private AntPathMatcher antPathMatcher = new AntPathMatcher();

	/**
	 * After completion.
	 *
	 * @param request  the request
	 * @param response the response
	 * @param arg2     the arg 2
	 * @param ex       the ex
	 *
	 * @throws Exception the exception
	 */
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object arg2, Exception ex) throws Exception {
		if (ex != null) {
			log.error("<== afterCompletion - 解析token失败. ex={}", ex.getMessage(), ex);
			this.handleException(response, ex);
		}
	}

	/**
	 * Post handle.
	 *
	 * @param request  the request
	 * @param response the response
	 * @param arg2     the arg 2
	 * @param mv       the mv
	 */
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object arg2, ModelAndView mv) {
	}

	/**
	 * Pre handle boolean.
	 *
	 * @param request  the request
	 * @param response the response
	 * @param handler  the handler
	 *
	 * @return the boolean
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		log.info("com.yuke.cloud.common.core.interceptor ==》TokenInterceptor权限拦截器 ==> preHandle方法");
		String uri = request.getRequestURI();
		log.info("<== preHandle - TokenInterceptor权限拦截器1.  url={}", uri);

		//todo 开发环境时放开，生产环境下注释掉或spring.profiles.active的值为prod add by wg 20180910
		if ("dev".equals(env)) { //开发环境忽略签名认证
			log.info("<== preHandle - 开发环境不走认证.  url={}", uri);
			return true;  // todo 开发环境下调试toke及认证，需注释掉 20181215
		}
		if (uri.contains(AUTH_PATH1) || uri.contains(AUTH_PATH2) || uri.contains(AUTH_PATH3) || uri.contains(AUTH_PATH4)
				|| uri.contains(MALL_PATH) || uri.contains(ALIPAY_CALL_URI) || uri.contains(WEIXINPAY_CALL_URI)
				|| uri.contains(ALIPAY_RECHARGE_CALL_URI) || uri.contains(WEIXINPAY_RECHARGE_CALL_URI) ) {
			log.info("<== preHandle - 配置URL不走认证.  url={}", uri);
			return true;
		}
		log.info("<== preHandle - 调试模式不走认证.  OPTIONS={}", request.getMethod().toUpperCase());

		if (OPTIONS.equalsIgnoreCase(request.getMethod())) {
			log.info("<== preHandle - 调试模式不走认证.  url={}", uri);
			return true;
		}

		if (isHaveAccess(handler)) {
			log.info("<== preHandle - 不需要认证注解不走认证.  token={}");
			return true;
		}

		//mod by wg 20181230 增加异常处理,如果头中带bearer等处理
//		String token = StringUtils.substringAfter(request.getHeader(HttpHeaders.AUTHORIZATION), "Bearer ");
		String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
		String authPrefix = "Bearer ";
		String token = "";
		if (PublicUtil.isNotEmpty(authHeader) && StringUtils.startsWithIgnoreCase(authHeader, authPrefix)) {
			token = StringUtils.substring(authHeader, authPrefix.length());
		}
		if (PublicUtil.isEmpty(token)) {
			log.error("没有token信息, 不允许操作");
			// add by wg 20181230 增加处理
			Map<String, Object> result = new HashMap<>(3);
			result.put("code", 99990401);
			result.put("message", "无访问权限");
			try {
				String json = JacksonUtil.toJson(result);
				response.setStatus(HttpStatus.UNAUTHORIZED.value());
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write(json);
			} catch (IOException ex) {

			}

			return false;
		}

		log.info("<== preHandle - 权限拦截器2.  token={}", token);
		// mod by wg 20190126 API权限通过redis缓存认证
//		LoginAuthDto loginUser = (UserTokenDto) redisTemplate.opsForValue().get(RedisKeyUtil.getAccessTokenKey(token));

		UserTokenDto userTokenDto = (UserTokenDto) redisTemplate.opsForValue().get(RedisKeyUtil.getAccessTokenKey(token));
		LoginAuthDto loginUser = userTokenDto;
		if (loginUser == null) {
			log.error("获取用户信息失败, 不允许操作");
			return false;
		}

		// mod by wg 20190126 根据redis进行API权限认证
//		log.info("<== preHandle - 权限拦截器.  loginUser={}", loginUser);
//		ThreadLocalMap.put(GlobalConstant.Sys.TOKEN_AUTH_DTO, loginUser);
//		log.info("<== preHandle - 权限拦截器.  url={}, loginUser={}", uri, loginUser);
//		return true;

		Set<String> path = Sets.newHashSet();
		path.add(AUTH_LOGIN_AFTER_URL);
		path.add(AUTH_LOGOUT_URL);
		path.addAll(userTokenDto.getAuthorityUrlList());
		for (final String authority : path) {
			if (antPathMatcher.match(authority, uri)) {
				log.info("<== preHandle - 权限拦截器3.  loginUser={}", loginUser.getLoginName());
				ThreadLocalMap.put(GlobalConstant.Sys.TOKEN_AUTH_DTO, loginUser);
				log.info("<== preHandle - 权限拦截器4.  url={}, loginUser={}", uri, loginUser.getLoginName());
				return true;
			}
		}

		log.error("<== preHandle - 权限拦截器,没有操作权限，.  url={}, loginUser={}", uri, loginUser.getLoginName());

		return false;
	}

	private void handleException(HttpServletResponse res, Exception ex) throws IOException {
		res.resetBuffer();
		//todo mod by wg 20181215 如果开发环境加上跨域配置（由于response直接输出，无法在配置类中进行配置，因此在response前直接配置跨域，生产环境在网关中已配置）
		if ("dev".equals(env)) { //开发环境对跨域进行配置
			res.setHeader("Access-Control-Allow-Origin", "*");
			res.setHeader("Access-Control-Allow-Credentials", "true");
			res.setHeader("Access-Control-Allow-Headers", "*");
		}
		res.setContentType("application/json");
		res.setCharacterEncoding("UTF-8");
		//如果res.getWriter().write有异常，可以用下面这个 mod by wg 20180914
//		res.getWriter().write("{\"code\":100009 ,\"message\" :\"解析token失败\"}");
		if (ex != null /* && (ex instanceof BusinessException)*/) {
			// 不能再添加响应消息，否则返回给端的数据会重复
//			String temp = "{\"code\":" + ((BusinessException) ex).getCode() + " ,\"message\" :\"" + ex.getMessage() + "\"}";
//			res.getOutputStream().write(temp.getBytes());
//			res.flushBuffer();
		} else {
			String temp = "{\"code\":100009 ,\"message\" :\"解析token失败\"}";
			res.getOutputStream().write(temp.getBytes());
			res.flushBuffer();
		}
	}

	private boolean isHaveAccess(Object handler) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;

		Method method = handlerMethod.getMethod();

		NoNeedAccessAuthentication responseBody = AnnotationUtils.findAnnotation(method, NoNeedAccessAuthentication.class);
		return responseBody != null;
	}

}
  